﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Data;
using System.Threading;
using System.Text.RegularExpressions;
using System.Data.Linq.Mapping;

namespace FCMS.Data.Linq
{
    public static class DataContextExtensions
    {
        private class AsyncResult<T> : IAsyncResult
        {
            private ManualResetEvent m_manualResetEvent = new ManualResetEvent(false);
            private bool m_isCompleted = false;
            private object m_asyncState;

            public AsyncResult(object asyncState)
            {
                this.m_asyncState = asyncState;
            }

            #region IAsyncResult Members

            public object AsyncState
            {
                get
                {
                    return this.m_asyncState;
                }
            }

            public WaitHandle AsyncWaitHandle
            {
                get
                {
                    return this.m_manualResetEvent;
                }
            }

            public bool CompletedSynchronously
            {
                get
                {
                    return false;
                }
            }

            public bool IsCompleted
            {
                get
                {
                    return this.m_isCompleted;
                }
            }

            #endregion

            public void Complete()
            {
                this.m_isCompleted = true;
                this.m_manualResetEvent.Set();
            }

            public T Result { get; set; }

            public Exception Exception { get; set; }
        }

        public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query)
        {
            return dataContext.ExecuteQuery<T>(query, false);
        }

        private static XmlMappingSource s_emptyMappingSource =
            XmlMappingSource.FromXml("<Database Name=\"MC_Data\" xmlns=\"http://schemas.microsoft.com/linqtosql/mapping/2007\" />");

        public static IEnumerable<T> Translate<T>(this DataContext dataContext, DbDataReader reader, bool loadByMembers)
        {
            if (!loadByMembers)
            {
                return dataContext.Translate<T>(reader).ToList();
            }

            DataContext emptyDataContext = new DataContext(dataContext.Connection, s_emptyMappingSource);
            return emptyDataContext.Translate<T>(reader);
        }

        public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query, bool withNoLock)
        {
            return dataContext.ExecuteQuery<T>(query, withNoLock, false);
        }

        public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query, bool withNoLock, bool loadByMembers)
        {
            DbCommand commnd = dataContext.GetCommand(query, withNoLock);

            dataContext.OpenConnection();

            using (DbDataReader reader = commnd.ExecuteReader())
            {
                return dataContext.Translate<T>(reader, loadByMembers).ToList();
            }
        }

        private static Regex s_withNoLockRegex = 
            new Regex(@"(] AS \[t\d+\])", RegexOptions.Compiled | RegexOptions.Singleline);

        private static string AddWithNoLock(string cmdText)
        {
            IEnumerable<Match> matches = s_withNoLockRegex.Matches(cmdText).Cast<Match>().OrderByDescending(m => m.Index);
            foreach (Match m in matches)
            {
                int splitIndex = m.Index + m.Value.Length;
                cmdText = cmdText.Substring(0, splitIndex) + " WITH (NOLOCK)" + cmdText.Substring(splitIndex);
            }

            return cmdText;
        }

        private static SqlCommand GetCommand(this DataContext dataContext, IQueryable query, bool withNoLock)
        {
            SqlCommand command = (SqlCommand)dataContext.GetCommand(query);

            if (withNoLock)
            {
                command.CommandText = AddWithNoLock(command.CommandText);
            }

            return command;
        }

        public static IAsyncResult BeginExecuteQuery(this DataContext dataContext, IQueryable query)
        {
            return dataContext.BeginExecuteQuery(query, false, null, null);
        }

        public static IAsyncResult BeginExecuteQuery(this DataContext dataContext, IQueryable query, bool withNoLock)
        {
            return dataContext.BeginExecuteQuery(query, withNoLock, null, null);
        }

        public static IAsyncResult BeginExecuteQuery(this DataContext dataContext, IQueryable query, AsyncCallback callback, object asyncState)
        {
            return dataContext.BeginExecuteQuery(query, false, callback, asyncState);
        }

        public static IAsyncResult BeginExecuteQuery(this DataContext dataContext, IQueryable query, bool withNoLock, AsyncCallback callback, object asyncState)
        {
            SqlCommand command = dataContext.GetCommand(query, withNoLock);
            dataContext.OpenConnection();

            AsyncResult<DbDataReader> asyncResult = new AsyncResult<DbDataReader>(asyncState);
            command.BeginExecuteReader(ar =>
            {
                try
                {
                    asyncResult.Result = command.EndExecuteReader(ar);
                }
                catch (Exception e)
                {
                    asyncResult.Exception = e;
                }
                finally
                {
                    asyncResult.Complete();
                    if (callback != null) callback(asyncResult);
                }
            }, null);

            return asyncResult;
        }

        public static List<T> EndExecuteQuery<T>(this DataContext dataContext, IAsyncResult ar)
        {
            AsyncResult<DbDataReader> asyncResult = (AsyncResult<DbDataReader>)ar;
            asyncResult.AsyncWaitHandle.WaitOne();

            if (asyncResult.Exception != null)
            {
                throw asyncResult.Exception;
            }

            using (DbDataReader reader = asyncResult.Result)
            {
                return dataContext.Translate<T>(reader).ToList();
            }
        }

        public static DbDataReader ExecuteSpReader(this DataContext dataContext, string spName, params DbParameter[] parameters)
        {
            DbCommand cmd = dataContext.Connection.CreateCommand();
            cmd.CommandText = spName;
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.Parameters.AddRange(parameters);

            dataContext.OpenConnection();
            return cmd.ExecuteReader();
        }

        public static int ExecuteSpNonQuery(this DataContext dataContext, string spName, params DbParameter[] parameters)
        {
            DbCommand cmd = dataContext.Connection.CreateCommand();
            cmd.CommandText = spName;
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.Parameters.AddRange(parameters);

            dataContext.OpenConnection();
            return cmd.ExecuteNonQuery();
        }

        public static DbDataReader ExecuteReader(this DataContext dataContext, string cmdText, params DbParameter[] parameters)
        {
            DbCommand cmd = dataContext.Connection.CreateCommand();
            cmd.CommandText = cmdText;
            cmd.Parameters.AddRange(parameters);

            dataContext.OpenConnection();
            return cmd.ExecuteReader();
        }

        public static object ExecuteScalar(this DataContext dataContext, string cmdText, params DbParameter[] parameters)
        {
            DbCommand cmd = dataContext.Connection.CreateCommand();
            cmd.CommandText = cmdText;
            cmd.Parameters.AddRange(parameters);

            dataContext.OpenConnection();
            return cmd.ExecuteScalar();
        }

        private static void OpenConnection(this DataContext dataContext)
        {
            if (dataContext.Connection.State == ConnectionState.Closed)
            {
                dataContext.Connection.Open();
            }
        }

    }
}
